

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import Handles
import graph
import math

headerSize = 30
connectorHeight = 30
itemWidth = 60

# property types
StringT = 1
NumberT = 2

class OperatorItem(QAbstractGraphicsShapeItem):
    """ Any operator on the graphics view is one of these """
    def __init__(self,x,y,inputs,outputs,name):
        """ An operator at x,y with defined ins and outs, and a name"""
        QGraphicsItem.__init__(self)
        self.signaller = QObject()
        self.setFlag(self.ItemIsMovable,True)
        self.x = x
        self.y = y
        self.setPos(x,y)
        self.name=name
       
        self.inputs = []
        self.addInputHandles(inputs)

        self.outputs = []
        self.addOutputHandles(outputs)

        self.makeGraph()
        
    def __repr__(self):
        return self.name
    
    def wheelEvent(self,event):
        # this seems to go to the scene first
        nd = float(event.delta())/100.0
        z=math.pow(0.9,float(nd))
        self.scale(z,z)
        event.accept()

    def mousePressEvent(self,event):
        self.oldPos = self.scenePos()
        QGraphicsItem.mousePressEvent(self,event)

    def mouseMoveEvent(self,event):
        QGraphicsItem.mouseMoveEvent(self,event)
        self.setSelected(False)
        self.signaller.emit(SIGNAL("operatorMove(PyQt_PyObject)"),self)

    def mouseReleaseEvent(self,event):
        if self.pos() != self.oldPos:
            command = CommandMoveOperatorItem(self,self.oldPos)
            self.scene().undoStack.push(command)
        QGraphicsItem.mouseReleaseEvent(self,event)

    def contextMenuEvent(self,event):
        """ Right mouse button - show the remove/properties menu """
        menu = QMenu()
        menu.move(event.scenePos().x(),event.scenePos().y())
        remove = menu.addAction("Remove")
        QObject.connect(remove, SIGNAL("triggered()"),self.remove)
        props = menu.addAction("Properties")
        if self.propDef():
            props.setEnabled(True)
        else:
            props.setEnabled(False)
        QObject.connect(props, SIGNAL("triggered()"),self.properties)
        menu.exec_(event.screenPos())

    def addInputHandles(self,names):
        [self.addInputHandle(n) for n in names]

    def addOutputHandles(self,names):
        [self.addOutputHandle(n) for n in names]

    def makeGraph(self):
        self.graph = graph.digraph()
        self.graph.add_node(self)
        for k in self.inputs:
            self.graph.add_node(k)
            self.graph.add_edge(k,self)
        for k in self.outputs:
            self.graph.add_node(k)
            self.graph.add_edge(self,k)
         
    def properties(self):
        pass
    def remove(self):
        scene = self.scene()
        scene.undoStack.beginMacro("Remove Operator")
        g = self.scene().graph
        ins = self.inputs
        outs = self.outputs
        for i in ins:
            for n in g.incidents(i):
                edgeItem = g.get_edge_attributes(n,i)['Edge']
                edgeItem.delete()
        print outs
        for o in outs:
            for n in g.neighbors(o):
                edgeItem = g.get_edge_attributes(o,n)['Edge']
                edgeItem.delete()
                
        command = CommandDeleteOperatorItem(self.scene(),self)
        scene.undoStack.push(command)
        scene.undoStack.endMacro()
    def canProcess(self):
        vs = [v == None for v in self.inputs.value]
        return not any(vs)

    def propDef(self):
        """ override to add properties to operators """
        return None

    def getDialogValues(self,ui):
        """ get the values from the dialog """
        v = {}
        for (vName,vType) in self.propDef().iteritems():
            if vType == StringT:
                v[vName] = getattr(ui,vName).text()
            elif vType == NumberT:
                v[vName] = getattr(ui,vName).value()
            else:
                raise ValueError,"unknown prop type"
        return v
    def setDialogValues(self,ui):
        """ set the dialog with values from the props """
        for (vName,vType) in self.propDef().iteritems():
            print "setting ",vName," to ",self.props[vName]
            if vType == StringT:
                print "text"
                getattr(ui,vName).setText(self.props[vName])
            elif vType == NumberT:
                print "number"
                getattr(ui,vName).setValue(self.props[vName])


    
class OperatorBoxItem(OperatorItem):
    def __init__(self,x,y,inputs,outputs,name):
        self.width = itemWidth
        self.height=max(len(inputs),len(outputs))*connectorHeight + headerSize
        OperatorItem.__init__(self,x,y,inputs,outputs,name)
 
    def myPolygon(self):
        return QRectF(-self.width/2,-self.height/2,self.width,self.height)

    def boundingRect(self):
        return QRectF(-self.width/2,-self.height/2,self.width,self.height)

    def addOutputHandle(self,n):
        x = self.width/2.0
        y = -self.height/2.0 + headerSize + (connectorHeight * len(self.outputs)) + connectorHeight/2.0
        self.outputs.append(Handles.Outputs(x,y,n,self))
 
    def addInputHandle(self,n):
        x = -self.width/2.0
        y = -self.height/2.0 + headerSize + (connectorHeight * len(self.inputs)) + connectorHeight/2.0
        self.inputs.append(Handles.Inputs(x,y,n,self))

    def paint(self,painter,qstyle,widget):
#        brush=QBrush(Qt.white)
#        brush.setStyle(Qt.NoBrush)
        brush=self.brush()
        pen = QPen()
        pen.setWidth(0)
        pen.setColor(Qt.black)

        if self.isSelected():
            pen.setStyle(Qt.DashLine)
        else:
            pen.setStyle(Qt.SolidLine)

        painter.setBrush(brush)
#       painter.setPen(pen)

        if self.isSelected():
            painter.setOpacity(0.5)

        font=QFont()
        painter.setFont(font)

        painter.drawRect(self.myPolygon())
        
        painter.drawText(-self.width/2.0+2,-self.height/2.0+headerSize/2.0, QString(self.name))

class OperatorTextItem(QGraphicsTextItem,OperatorItem):
    def __init__(self,x,y,inputs,outputs,name):
        QGraphicsTextItem.__init__(self,None)
        OperatorItem.__init__(self,x,y,inputs,outputs,name)
        self.setPlainText(self.displayText())
    def paint(self,painter,qstyle,widget):
        brush=QBrush(Qt.white)
        brush.setStyle(Qt.NoBrush)
        pen = QPen()
        pen.setWidth(0)
        pen.setColor(Qt.black)

        if self.isSelected():
            pen.setStyle(Qt.DashLine)
        else:
            pen.setStyle(Qt.SolidLine)

        painter.setBrush(brush)
        painter.setPen(pen)

        if self.isSelected():
            painter.setOpacity(0.5)

        font=QFont()
        painter.setFont(font)
        bbox = painter.boundingRect(QRectF(0,0,0,0),Qt.AlignLeft,self.displayText())

#        painter.drawRect(self.myPolygon())
        r=bbox.adjusted(-5,-5,15,5)
        painter.drawRoundRect(r,50,50)
        painter.drawText(bbox, self.displayText())

    def locateOutputHandles(self):
        (x,y,w,h) = QGraphicsTextItem.boundingRect(self).getRect()
        for oHandle in self.outputs:
            oHandle.setPos(x+w+10,y+h/2.0)
            self.signaller.emit(SIGNAL("operatorMove(PyQt_PyObject)"),self)
    def addOutputHandle(self,n):
        self.outputs.append(Handles.Outputs(0,0,n,self))

    def addInputHandle(self,n):
        self.inputs.append(Handles.Inputs(-8,10,n,self))

    def boundingRect(self):
        b = QGraphicsTextItem.boundingRect(self)
        b.adjust(-5,-5,15,5)
        return b

class OpsButton(QPushButton):
    def __init__(self,klass,scene,parent):
        QPushButton.__init__(self,klass.name,parent)
        QObject.connect(self,SIGNAL("clicked()"),self.clicked)
        self.scene = scene
        self.klass=klass
    def clicked(self):
        center = self.scene.views()[0].viewport().rect().center()
        center = self.scene.views()[0].mapToScene(center)
        try:
            obj = self.klass(center.x(),center.y())
        except CancelException:
            return
        self.scene.addOperatorItem(obj)

class CancelException(Exception):
    def __init__(self):
        pass
    def __str__(self):
        return "User cancelled operator creation"

class CommandAddOperatorItem(QUndoCommand):
    def __init__(self,scene,item):
        QUndoCommand.__init__(self,"Delete operator")
        self.item = item
        self.scene = scene
    def redo(self):
        self.scene.addItem(self.item)
        self.scene.graph.add_graph(self.item.graph)
        self.item.signaller.emit(SIGNAL("operatorMove(PyQt_PyObject)"),self)
    def undo(self):
        self.scene.removeItem(self.item)
        nodes = self.item.graph.nodes()
        [self.scene.graph.del_node(n) for n in nodes]

class CommandMoveOperatorItem(QUndoCommand):
    def __init__(self,item,oldPos):
        QUndoCommand.__init__(self,"Move operator")
        self.item = item
        self.itemPos = item.scenePos()
        self.scene = item.scene()
        self.oldPos = oldPos
    def redo(self):
        self.item.setPos(self.itemPos)
        self.item.signaller.emit(SIGNAL("operatorMove(PyQt_PyObject)"),self)

    def undo(self):
        self.item.setPos(self.oldPos)
        self.item.signaller.emit(SIGNAL("operatorMove(PyQt_PyObject)"),self)

class CommandDeleteOperatorItem(QUndoCommand):
    """ in and out edges must have been deleted first """
    def __init__(self,scene,item):
        QUndoCommand.__init__(self,"Delete operator")
        self.item = item
        self.scene = scene
    def redo(self):
        self.scene.removeItem(self.item)
        nodes = self.item.graph.nodes()
        [self.scene.graph.del_node(n) for n in nodes]
    def undo(self):
        self.scene.addItem(self.item)
        self.scene.graph.add_graph(self.item.graph)

    
class OutputItem(OperatorTextItem):
    group = "output"
    def __init__(self,x,y,inputs,outputs,name):
        OperatorTextItem.__init__(self,x,y,inputs,outputs,name)

  
class InputItem(OperatorTextItem):
    group = "input"
    def __init__(self,x,y,inputs,outputs,name):
        OperatorTextItem.__init__(self,x,y,inputs,outputs,name)
